﻿using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using NiuX.Json;

namespace NiuX.Objects;

/// <summary>
/// 深拷贝
/// </summary>
public static class DeepCloneExtensions
{
    /// <summary>
    /// 使用二进制流进行深拷贝
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static T? DeepCloneWithBinaryFormatter<T>(this T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        if (source is null)
        {
            return default;
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();

        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }

    /// <summary>
    /// 使用 Json 序列化器进行深拷贝
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <returns></returns>
    public static T? DeepCloneWithJsonSerializer<T>(this T source) => source is null ? default : source.ToJson().FromJson<T>();

    /// <summary>
    /// 使用反射进行深拷贝
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static T? DeepCloneWithReflection<T>(this T obj)
    {
        //如果是字符串或值类型则直接返回
        if (obj == null || obj is string || obj.GetType().IsValueType) return obj;

        var retval = Activator.CreateInstance(obj.GetType());

        foreach (var field in obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
        {
            try
            {
                field.SetValue(retval, DeepCloneWithReflection(field.GetValue(obj)));
            }
            catch
            {
                // ignored
            }
        }

        return (T)retval;
    }
}